import { ConstantEnum } from '@libs/common/enums';
import {
  BaseRepository,
  indexBuilder,
  paginateBuilder,
} from '@libs/common/repository';
import { isPro } from '@libs/common/utils';
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { DataSource, In } from 'typeorm';

import { Product } from '@/market/product/entities/product.entity';
import { ProductService } from '@/market/product/product.service';

import { DeliverService } from '../deliver/deliver.service';
import { DeviceRoadService } from '../device-road/device-road.service';
import {
  DeviceCargoLaneProductVo,
  FindDeviceCargoLaneDto,
  IndexDeviceCargoLaneDto,
  OfflineDeviceCargoLaneDto,
  ReplenishDeviceCargoLaneDto,
  ShowDeviceCargoLaneDto,
  StoreDeviceCargoLaneDto,
  UpdateDeviceCargoLaneDto,
} from './dto/device-cargo-lane.dto';
import { DeviceCargoLane } from './entities/device-cargo-lane.entity';

function formatter(list: DeviceCargoLane[]) {
  const obj: Record<string, DeviceCargoLaneProductVo> = {};
  list.forEach((item) => {
    if (!item.product) return;

    const store = obj[item.product.id]
      ? obj[item.product.id].store + (item.store || 0)
      : item.store || 0;

    obj[item.product.id] = {
      id: item.id,
      store,
      productName: item.product.name,
      productId: item.product.id,
      productImage: item.product.image,
      productDiscountPrice: item.product.discountPrice,
      productOriginalPrice: item.product.originalPrice,
      productDescription: item.product.description,
    };
  });

  return Object.values(obj);
}

@Injectable()
export class DeviceCargoLaneService extends BaseRepository {
  constructor(
    @Inject(ConstantEnum.DB_DATA_SOURCE_MANAGER) dataSource: DataSource,
  ) {
    super(dataSource);
  }

  private repository = this.getRepository(DeviceCargoLane);

  private repositoryManager = () => this.getRepository(DeviceCargoLane);

  @Inject(DeliverService)
  private readonly deliverService: DeliverService;

  @Inject(DeviceRoadService)
  private readonly deviceRoadService: DeviceRoadService;

  @Inject(forwardRef(() => ProductService))
  private readonly productService: ProductService;

  async index(
    dto: IndexDeviceCargoLaneDto,
    user: SC.User.SysInfo,
    mcuid: string,
  ) {
    if (
      !['1000889434306048000', '1458182516067864576'].includes(user.uid) &&
      isPro
    ) {
      throw new Error('非管理员禁止操作');
    }

    const raw = indexBuilder({
      dto,
      repository: this.repository,
      relation: ['deviceRoad'],
    });

    raw
      .andWhere('deviceRoad.mcuid = :mcuid', { mcuid })
      .leftJoinAndMapOne(
        'DeviceCargoLane.product',
        Product,
        'product',
        'product.id = DeviceCargoLane.productId',
      );

    const res = await paginateBuilder(raw, dto);

    return res;
  }

  show<D extends ShowDeviceCargoLaneDto = ShowDeviceCargoLaneDto>(
    dto: D,
    mcuid: string,
  ) {
    const raw = this.repository
      .createQueryBuilder()
      .where('deviceRoad.mcuid = :mcuid', { mcuid })

      .leftJoinAndSelect('DeviceCargoLane.deviceRoad', 'deviceRoad')
      .leftJoinAndMapOne(
        'DeviceCargoLane.product',
        Product,
        'product',
        'product.id = DeviceCargoLane.productId',
      );

    if (dto.id) {
      raw.andWhere('DeviceCargoLane.id = :id', { id: dto.id });
    } else if (dto.ids) {
      raw.andWhere('DeviceCargoLane.id = :id', { id: In(dto.ids) });
    }

    return raw.getOne();
  }

  async update(mcuid: string, dto: UpdateDeviceCargoLaneDto) {
    const entity = await this.repository
      .createQueryBuilder()
      .where('DeviceCargoLane.id = :id', { id: dto.id })
      .leftJoinAndSelect('DeviceCargoLane.deviceRoad', 'deviceRoad')
      .andWhere('deviceRoad.mcuid = :mcuid', { mcuid })
      .getOne();

    if (!entity) return;

    const entities = plainToInstance(DeviceCargoLane, {
      ...entity,
      store: dto.store,
      productId: dto.productId,
    });

    await this.repositoryManager().update(dto.id, entities);

    return entities;
  }

  async init(mcuid: string) {
    const roads = await this.deviceRoadService.find({ mcuid });

    const instance = roads.map((item) => {
      return plainToInstance(DeviceCargoLane, {
        deviceRoad: item.id,
      });
    });

    const entity = this.repositoryManager().create(instance);

    await this.repositoryManager().insert(entity);

    return;
  }

  private findBase(mcuid: string, dto?: FindDeviceCargoLaneDto) {
    const raw = this.repository
      .createQueryBuilder()
      .leftJoinAndSelect('DeviceCargoLane.deviceRoad', 'deviceRoad')
      .where('deviceRoad.mcuid = :mcuid', { mcuid });

    if (dto && Object.keys(dto).length > 0) {
      raw.andWhere(dto);
    }

    return raw;
  }

  async findOne(mcuid: string, dto?: FindDeviceCargoLaneDto) {
    const raw = this.findBase(mcuid, dto)
      .leftJoinAndMapOne(
        'DeviceCargoLane.product',
        Product,
        'product',
        'product.id = DeviceCargoLane.productId',
      )
      .getOne();

    return raw;
  }

  async find(mcuid: string, dto?: FindDeviceCargoLaneDto) {
    const raw = this.findBase(mcuid, dto)
      .leftJoinAndMapOne(
        'DeviceCargoLane.product',
        Product,
        'product',
        'product.id = DeviceCargoLane.productId',
      )
      .getMany();
    return raw;
  }

  async findProduct(mcuid: string, dto?: FindDeviceCargoLaneDto) {
    const raw = this.findBase(mcuid, dto)
      .innerJoinAndMapOne(
        'DeviceCargoLane.product',
        Product,
        'product',
        'product.id = DeviceCargoLane.productId',
      )
      .getMany();
    return raw;
  }
  findIdByMcuid(id: string) {
    return this.repository
      .createQueryBuilder('DeviceCargoLane')
      .where('DeviceCargoLane.deviceRoad = :deviceRoad', { deviceRoad: id })
      .innerJoinAndSelect('DeviceCargoLane.deviceRoad', 'deviceRoad')
      .getOne();
  }

  /** 补货 */
  async replenish(
    dto: ReplenishDeviceCargoLaneDto,
    user: SC.User.SysInfo,
    mcuid: string,
  ) {
    if (
      (!user ||
        !['1000889434306048000', '1458182516067864576'].includes(user.uid)) &&
      isPro
    ) {
      throw new Error('非管理员禁止操作');
    }

    const { productId, id, number } = dto;

    if (!id) throw new Error('货道ID不能为空');
    if (!productId) throw new Error('商品ID不能为空');
    if (!number || number === 0) throw new Error('补货数量不能为0');

    const cargoLane = await this.findOne(mcuid, { id: id });

    if (!cargoLane) throw new Error('货道不存在');

    if (!cargoLane.store) {
      cargoLane.store = 0;
    }
    if (!cargoLane.product || cargoLane.productId !== productId) {
      const product = await this.productService.find({ id: productId });
      if (!product) throw new Error('商品不存在');

      cargoLane.product = product;
    }

    const cargoLaneMaxNumber = cargoLane.deviceRoad.axisZ; // 最大值
    const cargoLaneStore = cargoLane.store; // 当前货道库存
    const productStock = cargoLane.product.stock; // 商品库存

    if (cargoLane.product.id !== productId && cargoLaneStore !== 0) {
      throw new Error(
        '货道商品不一致,当前货道商品为：' + cargoLane.product.name,
      );
    }

    if (number > productStock) {
      throw new Error(`当前库存剩余：${productStock}件，商品不足${number}件`);
    }

    if (number + cargoLaneStore > cargoLaneMaxNumber) {
      throw new Error('补货数量不能大于货道最大容量');
    }

    cargoLane.store = cargoLaneStore + number;

    // 更新货道库存
    const entities = await this.update(mcuid, {
      id: id,
      productId: productId,
      store: cargoLane.store,
    });

    // 减少商品库存
    await this.productService.stock(
      cargoLane.product.id,
      cargoLane.product.stock - number,
    );

    return entities;
  }

  /** 出货 */
  async delivery(
    mcuid: string,
    dto: { id: string; productId: string; number: number; transNo: string },
  ) {
    // 获取当前货道
    const cargoLane = await this.findOne(mcuid, { id: dto.id });

    if (!cargoLane) throw new Error('货道不存在');

    const { product, store = 0 } = cargoLane;

    if (!product) throw new Error('商品不存在');

    if (product.id !== dto.productId)
      throw new Error(`商品不一致，当前货道商品为：${product.name}`);

    // 检查货道库存是否充足
    if (store <= 0) throw new Error('货道库存不足');

    if (store < dto.number) throw new Error('商品不足');

    // 调用兑换机出货接口，成功出货后，扣除用户的积分
    await this.deliverService.deliver({
      axisX: cargoLane.deviceRoad.axisX,
      axisY: cargoLane.deviceRoad.axisY,
      mcuid: cargoLane.deviceRoad.mcuid,
      transNo: dto.transNo,
    });

    return dto.transNo;
  }

  /** 商品货道库存 */
  async store(dto: StoreDeviceCargoLaneDto, mcuid: string) {
    const cargoLanes = await this.findProduct(mcuid, dto);
    const cargoLane = cargoLanes;

    if (!cargoLane?.length) throw new Error('商品不存在');

    const res = formatter(cargoLane);

    return res[0];
  }

  /**
   * 移动端
   * 聚合商品库存数据
   */
  async mobileFind(mcuid: string) {
    // 获取全部数据
    const all = await this.findProduct(mcuid);

    return formatter(all);
  }

  /** 下架商品 */
  async offline(dto: OfflineDeviceCargoLaneDto, mcuid) {
    const cargoLane = await this.findOne(mcuid, { id: dto.id });

    if (!cargoLane) throw new Error('货道不存在');

    if (!cargoLane.product) throw new Error('商品不存在');

    const store = cargoLane.store;

    if (store === 0) return;

    // 把剩余的商品库存加回去
    const product = await this.productService.findOne({
      id: cargoLane.product.id,
    });

    if (!product) {
      throw new Error('商品不存在');
    }

    await this.productService.stock(
      cargoLane.product.id,
      product?.stock + (store || 0),
    );

    await this.repositoryManager().update(cargoLane.id, {
      productId: undefined,
      store: 0,
    });

    return cargoLane;
  }
}
